home *** CD-ROM | disk | FTP | other *** search
- #!/usr/bin/perl -w
- #
- # $Id: Locator.pm,v 1.16 2003/08/04 04:57:02 solovam Exp $
- #
- # This file is a part of gkismet
- #
- # This program is free software; you can redistribute it and/or
- # modify it under the terms of the GNU General Public License
- # as published by the Free Software Foundation; either version 2
- # of the License, or (at your option) any later version.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with this program; if not, write to the Free Software
- # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- #
-
- #
- # Locator class
- #
- package Locator;
-
- use Gnome;
- use Math::Trig;
- use Math::Complex;
- use Misc;
- use BssConnObserver;
- use DrawingArea;
- use strict;
- @Locator::ISA = qw(BssConnObserver);
-
- my $minHeadingDist = 10;
- my $compassWidth = 400;
- my $compassHeight = 60;
- my @currCoordLabels = ('Longitude', 'Latitude', 'Bearing', 'Fix');
- my @netCoordLabels = ('Longitude', 'Latitude', 'Distance', 'Bearing');
-
- #
- # Constructor
- #
- sub new
- {
- my $type = shift;
- my $self = new BssConnObserver(@_);
- bless $self, $type;
-
- # Check if there are GPS data
- if(!defined $self->{'connection'}->getGps()->{'lat'} || $self->{'connection'}->getNetwork()->{$self->{'bssid'}}{'aggpoints'} == 0)
- {
- my $messagebox = new Gnome::MessageBox("No GPS data for network bssid: " . $self->{'bssid'}, 'error', 'Button_Ok');
- $messagebox->run_and_close();
- return undef;
- }
-
- # Init vars
- $self->{'mode'} = 'averageCenter';
- $self->{'lastLat'} = $self->{'connection'}->getGps()->{'lat'};
- $self->{'lastLon'} = $self->{'connection'}->getGps()->{'lon'};
- $self->{'headingAngle'} = 0;
- $self->recalculate();
-
- # Window
- my $window = new Gtk::Window('dialog');
- $window->signal_connect("delete_event", sub {$self->deleteHandler()});
- $window->set_title('Network locator ' . $self->{'connection'}->getNetwork()->{$self->{'bssid'}}{'ssid'} . ' ' . $self->{'bssid'});
- $self->{'window'} = $window;
-
- # Drawing area
- $self->{'drawingArea'} = new DrawingArea($compassWidth, $compassHeight, sub {$self->drawCompass()});
- my $compassFrame = new Gtk::Frame(undef);
- $compassFrame->add($self->{'drawingArea'}->getWidget());
- my $fixed = new Gtk::Fixed();
- $fixed->put($compassFrame, 0, 0);
- my $compassHbox= new Gtk::HBox($true, 0);
- $compassHbox->pack_start($fixed, $true, $false, 20);
-
- # Labels
- my $currCoordFrame = new Gtk::Frame(undef);
- $self->{'currCoordFrame'} = $currCoordFrame;
- $currCoordFrame->set_label('Current coordinates');
- my $currCoordVbox = new Gtk::VBox($false, 5);
- $currCoordFrame->add($currCoordVbox);
- for my $l (@currCoordLabels)
- {
- my $hbox = new Gtk::HBox($false, 1);
-
- my $nlabel = new Gtk::Label($l . ': ');
- $nlabel->set_justify('left');
- $hbox->pack_start($nlabel, $false, $false, 5);
-
- my $vlabel = new Gtk::Label('');
- $vlabel->set_justify('right');
- $hbox->pack_end($vlabel, $false, $false, 5);
-
- $currCoordVbox->pack_start($hbox, $false, $false, 3);
-
- $self->{'currCoordLabel'}{$l} = $vlabel;
- }
- my $netCoordFrame = new Gtk::Frame(undef);
- $self->{'netCoordFrame'} = $netCoordFrame;
- $netCoordFrame->set_label('Average network center');
- my $netCoordVbox = new Gtk::VBox($false, 5);
- $netCoordFrame->add($netCoordVbox);
- for my $l (@netCoordLabels)
- {
- my $hbox = new Gtk::HBox($false, 1);
-
- my $nlabel = new Gtk::Label($l . ': ');
- $nlabel->set_justify('left');
- $hbox->pack_start($nlabel, $false, $false, 5);
-
- my $vlabel = new Gtk::Label('');
- $vlabel->set_justify('right');
- $hbox->pack_end($vlabel, $false, $false, 5);
-
- $netCoordVbox->pack_start($hbox, $false, $false, 3);
-
- $self->{'netCoordLabel'}{$l} = $vlabel;
- }
- $self->updateLabels();
-
- # Buttons
- my $bbox = new Gtk::HButtonBox();
- $bbox->set_layout('spread');
- $bbox->set_spacing(5);
- my $buttonClose = new Gtk::Button("Close");
- $buttonClose->signal_connect( "clicked", sub {$self->closeClicked()});
- $bbox->add($buttonClose);
- my $buttonMode = new Gtk::Button("Mode");
- $buttonMode->signal_connect( "clicked", sub {$self->modeClicked($buttonMode)});
- $bbox->add($buttonMode);
-
- # Assemble
- my $hbox = new Gtk::HBox($true, 0);
- $hbox->pack_start($currCoordFrame, $true, $true, 20);
- $hbox->pack_start($netCoordFrame, $true, $true, 20);
- my $vbox = new Gtk::VBox($false, 10);
- $vbox->pack_start($compassHbox, $true, $false, 20);
- $vbox->pack_start($hbox, $true, $false, 0);
- $vbox->pack_start($bbox, $true, $false, 20);
- $window->add($vbox);
- $window->show_all();
-
- # Start updates
- $self->{'connection'}->addObserver($self);
-
- return $self;
- }
-
- #
- # Update labels
- #
- sub updateLabels
- {
- my $self = shift;
- $self->{'currCoordLabel'}{'Latitude'}->set_text(sprintf "%.6f", $self->{'lat'});
- $self->{'currCoordLabel'}{'Longitude'}->set_text(sprintf "%.6f", $self->{'lon'});
- $self->{'currCoordLabel'}{'Bearing'}->set_text(sprintf "%.0f", $self->{'headingAngle'});
- my $fix;
- if($self->{'fix'} == -1)
- {
- $fix = 'No signal';
- }
- elsif($self->{'fix'} == 2)
- {
- $fix = '2D';
- }
- elsif($self->{'fix'} == 3)
- {
- $fix = '3D';
- }
- else
- {
- $fix = 'NONE';
- }
- $self->{'currCoordLabel'}{'Fix'}->set_text($fix);
- $self->{'netCoordLabel'}{'Latitude'}->set_text(sprintf "%.6f", $self->{'centerLat'});
- $self->{'netCoordLabel'}{'Longitude'}->set_text(sprintf "%.6f", $self->{'centerLon'});
- $self->{'netCoordLabel'}{'Bearing'}->set_text(sprintf "%.0f", $self->{'centerAngle'});
- my $dist = '';
- if($self->{'gKismetApplication'}->{'preferences'}->getPref('units') eq 'metric')
- {
- if($self->{'dist'} < 1000)
- {
- $dist = sprintf("%.2fm", $self->{'dist'});
- }
- else
- {
- $dist = sprintf("%.3fkm", $self->{'dist'} / 1000);
- }
- }
- else
- {
- if($self->{'dist'} / $mileFactor > 0.5)
- {
- $dist = sprintf("%.3fmi", $self->{'dist'} / $mileFactor);
- }
- else
- {
- $dist = sprintf("%.2fft", $self->{'dist'} / $footFactor);
- }
- }
- $self->{'netCoordLabel'}{'Distance'}->set_text($dist);
- }
-
- #
- # Change network locator mode
- #
- sub modeClicked
- {
- my $self = shift;
- if($self->{'mode'} eq 'averageCenter')
- {
- $self->{'mode'} = 'strongestSignal';
- $self->{'netCoordFrame'}->set_label('Strongest signal point');
- }
- elsif($self->{'mode'} eq 'strongestSignal')
- {
- $self->{'mode'} = 'averageCenter';
- $self->{'netCoordFrame'}->set_label('Average network center');
- }
- $self->recalculate();
- $self->drawCompass();
- $self->updateLabels();
- }
-
- #
- # Draw our compass into the pixmap
- #
- sub drawCompass
- {
- my $self = shift;
-
- # Abbreviations
- my $pixmap = $self->{'drawingArea'}->pixmap();
- my $gtkDrawingArea = $self->{'drawingArea'}->gtkDrawingArea();
-
- # Backdrop
- $pixmap->draw_rectangle($gtkDrawingArea->style()->black_gc(), $true,
- 0, 0, $gtkDrawingArea->allocation()->[2], $gtkDrawingArea->allocation()->[3]);
-
- # Middle horizontal white line
- $pixmap->draw_line($gtkDrawingArea->style()->white_gc(), 0, $compassHeight/2, $compassWidth, $compassHeight/2);
-
- # Red vertical line
- $pixmap->draw_line($self->{'drawingArea'}->{'redGC'}, $compassWidth/2, 0, $compassWidth/2, $compassHeight);
-
- # Digits on the gauge every 30 degrees
- for(my $i = 0; $i < 12; $i++)
- {
- # Linear position of a mark
- my $l = $self->deg2pix($i * 30);
- # Draw a mark
- $pixmap->draw_line($gtkDrawingArea->style()->white_gc(), $l, $compassHeight / 2 + 5, $l, $compassHeight / 2 - 5);
- # Draw a label
- $pixmap->draw_string($gtkDrawingArea->style()->font(), $gtkDrawingArea->style()->white_gc(),
- # Shift text left by half of string length
- $l - $self->{'drawingArea'}->stringWidth($i * 30) / 2,
- # Shift text down from the middle by mark height (5) + padding (5) + string height
- $compassHeight / 2 + 5 + $self->{'drawingArea'}->stringHeight($i * 30) + 5,
- $i * 30);
- }
-
- # North, south, west, east letters. Pretty mach same as above
- for my $i ([0, 'N'], [90, 'E'], [180, 'S'], [270, 'W'])
- {
- my $l = $self->deg2pix($i->[0]);
- $pixmap->draw_string($gtkDrawingArea->style()->font(), $gtkDrawingArea->style()->white_gc(),
- $l - $self->{'drawingArea'}->stringWidth($i->[1]) / 2,
- $compassHeight / 2 - 5 - 5,
- $i->[1]);
- }
-
- # Linear position of the center angle mark
- my $l = $self->deg2pix($self->{'centerAngle'});
- # A little triangle
- my @triangle = ($l - 5, $compassHeight, $l, $compassHeight - 5, $l + 5, $compassHeight);
- $pixmap->draw_polygon($self->{'drawingArea'}->{'redGC'}, $true, @triangle);
-
- # Redraw us
- $gtkDrawingArea->draw();
-
- return;
- }
-
- #
- # Translate degress into pixels on our compass scale
- #
- sub deg2pix
- {
- my $self = shift;
- my $deg = shift;
-
- my $l = ($deg - $self->{'headingAngle'}) * $compassWidth / 360 + $compassWidth * 3/2;
-
- while ($l >= $compassWidth)
- {
- $l -= $compassWidth;
- }
-
- return $l;
- }
-
- #
- # Handle an update from an observable
- #
- sub update
- {
- my $self = shift;
- my $object = shift;
- my $data = shift;
-
- if($object->isa('Connection') && defined $self->{'connection'} && $self->{'connection'} eq $object)
- {
- if($data->{'changed'} eq 'gps' || ($data->{'changed'} eq 'network' && $data->{'bssid'} eq $self->{'bssid'}))
- {
- $self->recalculate();
- $self->drawCompass();
- $self->updateLabels();
- }
- }
- }
-
- #
- # Repopulate coordinates, recalculate bearings and distance
- #
- sub recalculate
- {
- my $self = shift;
-
- if($self->{'mode'} eq 'averageCenter')
- {
- $self->{'centerLat'} = $self->{'connection'}->getNetwork()->{$self->{'bssid'}}{'agglat'} /
- $self->{'connection'}->getNetwork()->{$self->{'bssid'}}{'aggpoints'};
- $self->{'centerLon'} = $self->{'connection'}->getNetwork()->{$self->{'bssid'}}{'agglon'} /
- $self->{'connection'}->getNetwork()->{$self->{'bssid'}}{'aggpoints'};
- }
- elsif($self->{'mode'} eq 'strongestSignal')
- {
- $self->{'centerLat'} = $self->{'connection'}->getNetwork()->{$self->{'bssid'}}{'bestlat'};
- $self->{'centerLon'} = $self->{'connection'}->getNetwork()->{$self->{'bssid'}}{'bestlon'};
- }
-
- $self->{'lat'} = $self->{'connection'}->getGps()->{'lat'};
- $self->{'lon'} = $self->{'connection'}->getGps()->{'lon'};
- $self->{'fix'} = $self->{'connection'}->getGps()->{'fix'};
-
- $self->{'centerAngle'} = Locator->calcAngle($self->{'lat'}, $self->{'lon'}, $self->{'centerLat'}, $self->{'centerLon'});
- $self->{'dist'} = Locator->earthDistance($self->{'lat'}, $self->{'lon'}, $self->{'centerLat'}, $self->{'centerLon'});
-
- if(Locator->earthDistance($self->{'lastLat'}, $self->{'lastLon'}, $self->{'lat'}, $self->{'lon'}) >= $minHeadingDist)
- {
- $self->{'headingAngle'} = Locator->calcAngle($self->{'lastLat'}, $self->{'lastLon'}, $self->{'lat'}, $self->{'lon'});
- $self->{'lastLat'} = $self->{'lat'};
- $self->{'lastLon'} = $self->{'lon'};
- }
- }
-
- #
- # Get Gtk widget
- #
- sub getWidget
- {
- my $self = shift;
- return $self->{'window'};
- }
-
- #
- # Distance from the center of Earth in meters
- #
- # Taken (with some mods) from Kismet
- #
- sub calcRad
- {
- my $type = shift;
- my $lat = shift;
- $lat = deg2rad($lat);
-
- # the radius of curvature of an ellipsoidal Earth in the plane of the
- # meridian is given by
- #
- # R' = a * (1 - e^2) / (1 - e^2 * (sin(lat))^2)^(3/2)
- #
- # where a is the equatorial radius,
- # b is the polar radius, and
- # e is the eccentricity of the ellipsoid = sqrt(1 - b^2/a^2)
- #
- # a = 6378 km (3963 mi) Equatorial radius (surface to center distance)
- # b = 6356.752 km (3950 mi) Polar radius (surface to center distance)
- # e = 0.081082 Eccentricity
-
- my $a = 6378.137;
- my $e2 = 0.081082 * 0.081082;
- my $sc = sin($lat);
- my $x = $a * (1.0 - $e2);
- my $z = 1.0 - $e2 * $sc * $sc;
- my $y = Misc->pow($z, 1.5);
- my $r = $x / $y;
- $r = $r * 1000;
-
- return $r;
- }
-
- #
- # Distance between points in meters
- #
- # Taken (with some mods) from Kismet
- #
- sub earthDistance
- {
- my $type = shift;
- my ($lat1, $lon1, $lat2, $lon2) = (@_);
- my $x1 = Locator->calcRad($lat1) * cos(deg2rad($lon1)) * sin(deg2rad(90 - $lat1));
- my $x2 = Locator->calcRad($lat2) * cos(deg2rad($lon2)) * sin(deg2rad(90 - $lat2));
- my $y1 = Locator->calcRad($lat1) * sin(deg2rad($lon1)) * sin(deg2rad(90 - $lat1));
- my $y2 = Locator->calcRad($lat2) * sin(deg2rad($lon2)) * sin(deg2rad(90 - $lat2));
- my $z1 = Locator->calcRad($lat1) * cos(deg2rad(90 - $lat1));
- my $z2 = Locator->calcRad($lat2) * cos(deg2rad(90 - $lat2));
- my $a = acos(($x1 * $x2 + $y1 * $y2 + $z1 * $z2) / Misc->pow(Locator->calcRad(($lat1 + $lat2) / 2), 2));
- my $dist = Locator->calcRad(($lat1 + $lat2) / 2) * $a;
- if(ref($dist) eq 'Math::Complex')
- {
- return 0;
- }
- return $dist;
- }
-
- #
- # Azimuth
- #
- # Taken (with some mods) from Kismet
- #
- sub calcAngle
- {
- my $type = shift;
- my($lat1, $lon1, $lat2, $lon2) = (@_);
-
- my $R = Locator->calcRad($lat2);
-
- $lat1 = deg2rad($lat1);
- $lon1 = deg2rad($lon1);
- $lat2 = deg2rad($lat2);
- $lon2 = deg2rad($lon2);
-
- my $angle;
- # we are moving along parallel
- if($lat1 == $lat2)
- {
- # east
- if($lon2 > $lon1)
- {
- $angle = pi/2;
- }
- # west
- elsif($lon2 < $lon1)
- {
- $angle = 3 * pi / 2;
- }
- # not moving at all
- else
- {
- return(0);
- }
- }
- # we are moving along meridian
- elsif($lon1 == $lon2)
- {
- # north
- if($lat2 > $lat1)
- {
- $angle = 0;
- }
- # south
- elsif($lat2 < $lat1)
- {
- $angle = pi;
- }
- # we never get here
- else
- {
- return(undef);
- }
- }
- # otherwise we calculate heading
- else
- {
- my $tx = $R * cos($lat1) * ($lon2 - $lon1);
- my $ty = $R * ($lat2 - $lat1);
- $angle = atan($tx / $ty);
-
- if ($ty < 0)
- {
- $angle += pi;
- }
- if($angle >= (2 * pi))
- {
- $angle -= 2 * pi;
- }
- if($angle < 0)
- {
- $angle += 2 * pi;
- }
- }
-
- return rad2deg($angle);
- }
-
- 1;
-